﻿using System.Diagnostics;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Configuration;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.Options;
using Serilog.Enrichers.Sensitive;
using Microsoft.AspNetCore.HttpLogging;

namespace Cngot.Extensions.DependencyInjection
{
    public static class LoggerExtensions
    {
        const string TraceSourceContext = "Microsoft.AspNetCore.HttpLogging.HttpLoggingMiddleware";
        public static IServiceCollection AddCngotHttpLogging(this IServiceCollection services, Action<HttpLoggingOptions> action)
        {
            services.AddHttpLogging(action);
            services.AddHttpLoggingInterceptor<HttpLoggingInterceptor>();

            services.AddTransient<HttpContextHeaderMessageHandler>();
            services.ConfigureHttpClientDefaults(options =>
            {
                options.AddHttpMessageHandler<HttpContextHeaderMessageHandler>();
            });

            return services;
        }
        public static ILoggerProvider AddCngotLogging(this IServiceCollection services, Action<LoggerOption> action, Action<ILoggerProvider> cfg)
        {
            Activity.DefaultIdFormat = ActivityIdFormat.W3C;

            services.Configure(action);


            ILoggerProvider loggerProvider = new LoggerProvider();

            services.AddSerilog((sp, logger) =>
            {
                var loggerOption = sp.GetRequiredService<IOptions<LoggerOption>>().Value;

                if (string.IsNullOrEmpty(loggerOption.AppId))
                {
                    throw new ArgumentException("AppId不能为空");
                }

                loggerProvider.Init(sp, logger);

                var configration = sp.GetRequiredService<IConfiguration>();
                if (!string.IsNullOrEmpty(loggerOption.SensitiveWords))
                {
                    logger.Enrich.WithSensitiveDataMasking(options =>
                    {
                        options.MaskValue = loggerOption.MaskValue;
                        options.MaskProperties.AddRange(loggerOption.SensitiveWords.Split(',').Select(w => w.Trim()));
                    });
                }

                logger
                    .Enrich.WithProperty("AppId", loggerOption.AppId)
                    .Enrich.WithProperty("MessageId", loggerOption.MessageId.Invoke(sp));

                if (loggerOption.DefaultProperty != null && loggerOption.DefaultProperty.Count() > 0)
                {
                    foreach (var property in loggerOption.DefaultProperty)
                    {
                        logger.Enrich.WithProperty(property.Key, property.Value);
                    }
                }

                logger.Filter.With<HttpRemoveLogEventFilter>();

                logger.Enrich.WithCallerInfo(
                        includeFileInfo: true,
                        allowedAssemblies: loggerOption.AllowedAssemblies,
                        prefix: null)
                    .ReadFrom.Configuration(configration)
                    .Enrich.FromLogContext(); // 启用调用者信息富化器;

                cfg?.Invoke(loggerProvider);
            });

            return loggerProvider;
        }

        public static IApplicationBuilder UseCngotHttpLogging(this IApplicationBuilder app)
        {
            app.UseHttpLogging();
            return app;
        }

        public static ILoggerProvider UseFileAsync(this ILoggerProvider logger, Action<FileLoggingOption> action)
        {
            var loggerOption = logger.ServiceProvider.GetRequiredService<IOptions<LoggerOption>>().Value;
            string appId = loggerOption.AppId;

            var opt = new FileLoggingOption();
            action.Invoke(opt);

            if (opt.FileLogPathRequireAppId)
            {
                opt.FileLogPath += appId;
            }
            else
            {
                opt.FileLogPath = opt.FileLogPath.TrimEnd('/');
            }

            Environment.SetEnvironmentVariable("FileLogPath", opt.FileLogPath);

            string serilogVerbose = opt.FileLogPath + "/trace/.log";
            string serilogDebug = opt.FileLogPath + "/debug/.log";
            string serilogInfo = opt.FileLogPath + "/information/.log";
            string serilogWarn = opt.FileLogPath + "/warning/.log";
            string serilogError = opt.FileLogPath + "/error/.log";
            string serilogFatal = opt.FileLogPath + "/fatal/.log";

            var format = new TraceRenderedCompactJsonFormatter();

            logger.Logger
                .WriteTo.Logger(lg => lg.Filter.ByIncludingOnly(p => p.Level == LogEventLevel.Verbose || (p.Level == LogEventLevel.Information && p.Properties.FirstOrDefault(w => w.Key == "SourceContext").Value?.ToString().Replace("\"", "") == TraceSourceContext)).WriteTo.Async(a => a.File(formatter: format, serilogVerbose, rollingInterval: RollingInterval.Day, fileSizeLimitBytes: opt.FileSizeLimitBytes, retainedFileCountLimit: opt.RetainedFileCountLimit)))
                .WriteTo.Logger(lg => lg.Filter.ByIncludingOnly(p => p.Level == LogEventLevel.Debug).WriteTo.Async(a => a.File(formatter: format, serilogDebug, rollingInterval: RollingInterval.Day)))
                .WriteTo.Logger(lg => lg.Filter.ByIncludingOnly(p => p.Level == LogEventLevel.Information && !p.Properties.Any(w => w.Key == "SourceContext" & w.Value?.ToString().Replace("\"", "") == TraceSourceContext)).WriteTo.Async(a => a.File(formatter: format, serilogInfo, rollingInterval: RollingInterval.Day, fileSizeLimitBytes: opt.FileSizeLimitBytes, retainedFileCountLimit: opt.RetainedFileCountLimit)))
                .WriteTo.Logger(lg => lg.Filter.ByIncludingOnly(p => p.Level == LogEventLevel.Warning).WriteTo.Async(a => a.File(formatter: format, serilogWarn, rollingInterval: RollingInterval.Day, fileSizeLimitBytes: opt.FileSizeLimitBytes, retainedFileCountLimit: opt.RetainedFileCountLimit)))
                .WriteTo.Logger(lg => lg.Filter.ByIncludingOnly(p => p.Level == LogEventLevel.Error).WriteTo.Async(a => a.File(formatter: format, serilogError, rollingInterval: RollingInterval.Day, fileSizeLimitBytes: opt.FileSizeLimitBytes, retainedFileCountLimit: opt.RetainedFileCountLimit)))
                .WriteTo.Logger(lg => lg.Filter.ByIncludingOnly(p => p.Level == LogEventLevel.Fatal).WriteTo.Async(a => a.File(formatter: format, serilogFatal, rollingInterval: RollingInterval.Day, fileSizeLimitBytes: opt.FileSizeLimitBytes, retainedFileCountLimit: opt.RetainedFileCountLimit)));

            return logger;
        }
        public static ILoggerProvider UseHttpAsync(this ILoggerProvider logger, Action<HttpLoggerOption> action)
        {
            HttpLoggerOption opt = new HttpLoggerOption();
            action.Invoke(opt);
            logger.Logger.WriteTo.Http(
                requestUri: opt.RequestUri,
                queueLimitBytes: opt.QueueLimitBytes,
                //httpClient: new LogHttpClient(appId),
                period: opt.Period
            //textFormatter: new JsonFormatter()
            );
            return logger;
        }

    }
}